package ca.uvic.group2.api;

import java.util.*;
import java.io.Serializable;


class MovingAvg implements Serializable {
    public int[] avg = new int[50];
    public int index = 0;
    public long total = 0;
    public float commAvg = 0.0f;

    public MovingAvg() {
        reset();
    }

    public MovingAvg(MovingAvg src) {
        avg = src.avg.clone();
        index = src.index;
        total = src.total;
        commAvg = src.commAvg;
    }

    public void reset() {
        avg = new int[50];
        index = 0;
        total = 0;
        commAvg = 0.0f;
    }


    public void addValue(int val) {
        avg[index++] = val;
        total++;
        if (index >= avg.length) {
            index = 0;
        }

        this.commAvg += ((float)val - this.commAvg) / (float)total;
    }

    public float getAvg() {
        long len;
        int start;
        if (total < avg.length) {
            len = total;
            start = 0;
        } else {
            len = avg.length;
            start = index;
        }

        long avgTotal = 0;
        for (int i = 0; i < len; ++i) {
            avgTotal += avg[start++];
            if (start >= avg.length) {
                start = 0;
            }
        }

        return ((float)avgTotal) / len;
    }

    public float getCommAvg() {
        return commAvg;
    }

}

public class Stats implements Serializable {
    public static final int DB = 0;
    public static final int TRANSACTION = 1;
    public static final int QUOTE = 2;
    public static final int STOCKSERV = 3;

    public int kind;
    public String name;
    public long startTime;
    public long endTime;
    public int totalTransactions;
    public int totalQueries;
    public int totalErrors;
    public int currentSessions;
    public int maxSessions;
    public int avgTransactionTime;
    public MovingAvg avgQueryTime = new MovingAvg();
    public int readQueryCount;
    public int writeQueryCount;
    public int[] movingAvgTime = new int[50];
    public long[] movingAvgTotalTime = new long[50];
    public int movingAvgIndex = 0;
    public int movingAvgTotal = 0;
    public MovingAvg avgQueueTime = new MovingAvg();
    public MovingAvg avgExeTime = new MovingAvg();
    public long freeMem = 0;
    public long maxMem = 0;
    public long totalMem = 0;
    public MovingAvg avgQueueSize = new MovingAvg();

    public Stats(int kind, String name) {
        this.kind = kind;
        this.name = name;
        this.startTime = System.currentTimeMillis();
                    this.readQueryCount = 0;
                    this.writeQueryCount = 0;
    }

    public Stats(Stats src) {
        this.kind = src.kind;
        this.name = src.name;
        startTime = src.startTime;
        endTime = src.endTime;
        totalTransactions = src.totalTransactions;
        totalErrors = src.totalErrors;
        currentSessions = src.currentSessions;
        maxSessions = src.maxSessions;
        avgTransactionTime = src.avgTransactionTime;
        avgQueryTime = new MovingAvg(src.avgQueryTime);
        readQueryCount = src.readQueryCount;
        writeQueryCount = src.writeQueryCount;
        movingAvgTime = src.movingAvgTime.clone();
        movingAvgTotalTime = src.movingAvgTotalTime.clone();
        movingAvgIndex = src.movingAvgIndex;
        movingAvgTotal = src.movingAvgTotal;
        avgQueueTime = new MovingAvg(src.avgQueueTime);
        avgExeTime = new MovingAvg(src.avgExeTime);
        Runtime runtime = Runtime.getRuntime();
        freeMem = runtime.freeMemory();
        maxMem = runtime.maxMemory();
        totalMem = runtime.totalMemory();
        avgQueueSize = new MovingAvg(src.avgQueueSize);
    }

    public long getFreeMem() {
        return freeMem;
    }

    public long getMaxMem() {
        return maxMem;
    }

    public long getTotalMem() {
        return totalMem;
    }

    public Stats clone() {
        Stats copy;
        synchronized (this) {
            copy = new Stats(this);
        }
        if (copy.startTime > 0L && copy.endTime == 0L) {
            copy.endTime = System.currentTimeMillis();
        }
        return copy;
    }

    public float getTransactionsPerSecond() {
        float time = (endTime - startTime) / 1000.0f;
        if (time > 0) {
            return ((float)totalTransactions) / time;
        }
        return -1.0f;
    }

    public float getAvgTransactionTime() {
        return avgTransactionTime;
    }

    public float getAvgQueryTime() {
        return avgQueryTime.getAvg();
    }

    public float getCommAvgQueryTime() {
        return avgQueryTime.getCommAvg();
    }

    public float getMovingAvgTime() {
        long total = 0;
        int n = Math.min(movingAvgTotal, movingAvgTime.length);
        for (int i = 0; i < n; ++i) {
            total += movingAvgTime[i];
        }
        return ((float)total) / n;
    }

    public float getMovingAvgTransPerSecond() {

        if (movingAvgTotal == 0) {
            return -1.0f;
        }

        float diff;
        if (movingAvgTotal < movingAvgTime.length) {
            diff = ((float)(movingAvgTotalTime[movingAvgIndex - 1] - movingAvgTotalTime[0])) / 1000.0f;
            return ((float)movingAvgTotal) / diff;
        }

        int top = movingAvgIndex - 1;
        if (top < 0) {
            top = movingAvgTime.length - 1;
        }
        diff = ((float)(movingAvgTotalTime[top] - movingAvgTotalTime[movingAvgIndex])) / 1000.0f;
        return ((float)movingAvgTotalTime.length) / diff;
    }

    public synchronized void reset() {
        startTime = 0L;
        endTime = 0L;
        totalTransactions = 0;
        totalErrors = 0;
        //currentSessions = 0;
        maxSessions = currentSessions;
        avgTransactionTime = 0;
        avgQueryTime.reset();
        readQueryCount = 0;
        writeQueryCount = 0;
        movingAvgTime = new int[50];
        movingAvgTotalTime = new long[50];
        movingAvgIndex = 0;
        movingAvgTotal = 0;
        avgQueueTime.reset();
        avgExeTime.reset();
    }

    public void start() {
        reset();
        startTime = System.currentTimeMillis();
    }

    public void stop() {
        if (endTime == 0L) {
            endTime = System.currentTimeMillis();
        }
    }

    public boolean isCollecting() {
        return (this.startTime != 0L && this.endTime == 0L);
    }

    public void addTransaction(long time) {
        if (this.startTime != 0L && this.endTime == 0L) {
            long currentTime = System.currentTimeMillis();
            time = currentTime - time;
            synchronized (this) {
                this.avgTransactionTime += (time - this.avgTransactionTime) / ++this.totalTransactions;
                this.movingAvgTime[this.movingAvgIndex] = (int)time;
                this.movingAvgTotalTime[this.movingAvgIndex] = currentTime;

                if (++this.movingAvgIndex >= this.movingAvgTime.length) {
                    this.movingAvgIndex = 0;
                    this.movingAvgTotal = this.movingAvgTime.length;
                }
                ++this.movingAvgTotal;
            }
        }
    }

    public void addQuery(long time) {
        if (this.startTime != 0L && this.endTime == 0L){
            time = System.currentTimeMillis() - time;
            synchronized (this) {
                this.avgQueryTime.addValue((int)time);
            }
        }
    }

    public void addError() {
        if (this.startTime != 0L && this.endTime == 0L) {
            synchronized (this) {
                ++this.totalErrors;
            }
        }
    }

    public void addSession() {
        if (this.startTime != 0L && this.endTime == 0L) {
            synchronized (this) {
                if (++this.currentSessions > this.maxSessions) {
                    this.maxSessions = this.currentSessions;
                }
            }
        }
    }

    public void removeSession() {
        if (this.startTime != 0L && this.endTime == 0L) {
            synchronized (this) {
                --this.currentSessions;
            }
        }
    }

    public int getReadQueryCount(){
      return readQueryCount;
    }

    public void incReadQueryCount(int cnt){
        this.readQueryCount += cnt;
    }

    public int getWriteQueryCount(){
      return writeQueryCount;
    }

    public void incWriteQueryCount(int cnt){
        this.writeQueryCount += cnt;
    }

    public void addQueueTime(int time) {
        if (this.startTime != 0L && this.endTime == 0L){
            synchronized (this) {
                avgQueueTime.addValue(time);
            }
        }
    }

    public void addExeTime(int time) {
        if (this.startTime != 0L && this.endTime == 0L){
            synchronized (this) {
                avgExeTime.addValue(time);
            }
        }
    }

    public void addQueueSize(long size) {
        if (this.startTime != 0L && this.endTime == 0L){
            synchronized (this) {
                avgQueueSize.addValue((int)size);
            }
        }
    }

    public float getAvgQueueSize() {
        return avgQueueSize.getAvg();
    }

    public float getCommAvgQueueSize() {
        return avgQueueSize.getCommAvg();
    }

    public float getAvgQueueTime() {
        synchronized (avgQueueTime) {
            return avgQueueTime.getAvg();
        }
    }

    public float getCommAvgQueueTime() {
        return avgQueueTime.getCommAvg();
    }

    public float getCommAvgExeTime() {
        return avgExeTime.getCommAvg();
    }

    public float getAvgExeTime() {
        synchronized (avgExeTime) {
            return avgExeTime.getAvg();
        }
    }
}
